/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

// espefuse.py -p COM7 set_flash_voltage 3.3V , BURN: to program flash voltage selection efuses to 3.3V to release the gpio12
// idf.py -p COM7 build flash monitor

#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/stream_buffer.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "addr_from_stdin.h"

#include "cJSON.h"
#include "driver/uart.h"

#include "timer_x.h"
#include "wifi_x.h"
#include "ad768x.h"
#include "gpio_x.h"

#define USE_JSON 1

static TaskHandle_t timer_task = NULL;

// uint16_t c = 0;
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
    xTaskNotifyGive(timer_task);
    return pdTRUE;
}

static const char *TAG = "m_debug";

typedef enum
{
    not_valid_cmd = 0,
    request_info,
} m_uart_cmd_t;

#define CMD_QUEUE_LENGTH 8
#define CMD_QUEUE_ITEM_SIZE sizeof(m_uart_cmd_t)

#define uart_send_QUEUE_LENGTH 32
#define uart_send_QUEUE_ITEM_SIZE sizeof(send_message_t)

// static QueueHandle_t timer_queue;
static QueueHandle_t cmd_queue;
// static QueueHandle_t uart_send_queue;
char gl_mac_str[] = "00:00:00:00:00:00";

typedef struct 
{
    uint16_t data[80];
} data_pkg_t;

#define DATA_QUEUE_LENGTH 4
#define DATA_QUEUE_ITEM_SIZE sizeof(data_pkg_t)

static QueueHandle_t data_queue;

#define cmd_key_mac_str "mac"
#define cmd_key_str "cmd"
#define cmd_request_str "request"
#define cmd_request "device information"

#define EX_UART_NUM UART_NUM_0
#define PATTERN_CHR_NUM (3) /*!< Set the number of consecutive and identical characters received by receiver which defines a UART pattern*/
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)
static QueueHandle_t uart0_queue;

//  - ----------------------- gpio

#define GPIO_BTN_NUM GPIO_NUM_5
#define ESP_INTR_FLAG_DEFAULT 0
// static QueueHandle_t gpio_evt_queue = NULL;
TimerHandle_t xTimers_led;
// int led_flicker_count = 2;

//  - -----------------------
char gl_ssid_str[32] = "10.42.31.246";
char gl_pw_str[64] = "12341234";
char gl_ip_str[] = "192.168.254.254";
char gl_port_str[] = "65534";

#define new_ip_key_str "hostIP"
#define ssid_key_str "ssid"
#define pw_key_str "pw"
#define port_key_str "port"

const char nvs_ip_str[] = "hostIP"; // key
const char nvs_ssid_str[] = "ssid";
const char nvs_pw_str[] = "pw";
const char nvs_port_str[] = "port";

const float fs = 200; //not used in this program, just for check
//sample rate: check the timer_x.c, I use  
//.resolution_hz = 1 * 1000* 200 , // 200KHz, 1 tick = 5us
//.alarm_count = 125,                // period = 625us 
//625*8(ch)=5ms

// if use fs = 250:
// .resolution_hz = 1 * 1000 * 10, // 10KHz, 1 tick = 100us
// .alarm_count = 5,                // period = 500us 
//500*8(ch)=4ms

#define DATA_NUM_PKG 80 // so in fs=200,DATA_NUM_PKG=10: the packet will be sent every 50ms
#define DATA_NUM_PKG_LEN_IN_BYTE DATA_NUM_PKG*2

nvs_handle_t my_nvs_handler;

uint16_t eeg_u16_arr[DATA_NUM_PKG] = {0};

uint16_t eeg_u16_arr_counter = 0;
int int_data_eeg_array[DATA_NUM_PKG] = {0};
uint8_t ad_ch_counter = 0;

#define cmd_key_channel_num_str "chn" // number of channel
#define cmd_key_packet_num_str "pkn"

#define data_key_eeg_str "eeg"
#define number_of_1_channel_str "1"
uint16_t packet_num = 0;

uint8_t pkg_num_for_debug = 0;
char test_str[]="abcd";

static void udp_client_rcv_task(void *pvParameters)
{
    char rx_buffer[128];
    while (1)
    {
        wifi_recv_x(rx_buffer, sizeof(rx_buffer) - 1);
        // ESP_LOGI(TAG, "Received nothing, retry");
    }
    vTaskDelete(NULL);
}


void send_json_data_task(void *pParam)
{
    data_pkg_t d = {0};

    while (1)
    {
        if (xQueueReceive(data_queue, &d, portMAX_DELAY) != pdPASS)
        {
             ESP_LOGI(TAG, "something wrong, need check...1...");     
             continue;
        }

#ifdef USE_JSON
        // use json format - user friendly
        for (int i = 0; i < DATA_NUM_PKG; i++)
        {
            int_data_eeg_array[i] = d.data[i];
        }
        cJSON *root = cJSON_CreateObject();
        cJSON_AddStringToObject(root, cmd_key_mac_str, gl_mac_str);
        cJSON_AddNumberToObject(root, cmd_key_packet_num_str, packet_num++);
        cJSON_AddStringToObject(root, cmd_key_channel_num_str, number_of_1_channel_str);
        cJSON *p_data_eeg_Array = cJSON_CreateIntArray(int_data_eeg_array, DATA_NUM_PKG);
        cJSON_AddItemToObject(root, data_key_eeg_str, p_data_eeg_Array);

        char *p_json_str=NULL;
        p_json_str = (char *)malloc(2048);
        p_json_str = cJSON_PrintUnformatted(root);

        if (wifi_send_x(p_json_str, strlen(p_json_str)) < 0)
        {
            ESP_LOGI(TAG, "send error");
        }

        free(p_json_str);
        cJSON_Delete(root);
#else
        // use binary - low energy 
        if (wifi_send_x((char *)&d, DATA_NUM_PKG_LEN_IN_BYTE) < 0)
        {
            ESP_LOGI(TAG, "send error");
        }
#endif
        // if (wifi_send_x(test_str, strlen(test_str)) < 0)
        // {
        //     ESP_LOGI(TAG, "send error");
        // } 
        // ESP_LOGI(TAG, "pkg   : %d",pkg_num_for_debug++);
        // UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );   
        // ESP_LOGI(TAG, "space available   : %d",uxHighWaterMark);
    }
}

void deal_timer_task(void *pParam)
{
    // uint16_t eeg_u16 = 0;
    data_pkg_t d = {0};

    // int buff_available = 0;
    while (1)
    {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        d.data[eeg_u16_arr_counter++]=read_ad768x_data_one_data_on_chs(ad_ch_counter++);

        if(ad_ch_counter==8) {
            ad_ch_counter=0;
        }
        if (eeg_u16_arr_counter < DATA_NUM_PKG)
        {
            continue;
        }  
        eeg_u16_arr_counter = 0;

        BaseType_t r = xQueueSendToBack(data_queue, &d, 0);

        if (r == errQUEUE_FULL){
            ESP_LOGI(TAG, "data queue full ! ");
        }

    }
}

void m_deal_uart_cmd_task(void *pParam)
{
    // uint8_t write_char_data[1] = {0xFF};
    unsigned char info_str[] = {"mi_app v2 \r\n"};

    while (1)
    {
        m_uart_cmd_t c = {0};
        if (xQueueReceive(cmd_queue, &c, portMAX_DELAY) == pdPASS)
        {
            // if (c == unbind)
            // {
            //     esp_err_t ret = nvs_erase_key(my_nvs_handler, nvs_bound_mac_str);
            //     if (ret != ESP_ERR_NVS_NOT_FOUND)
            //     {
            //         ESP_ERROR_CHECK(ret);
            //     }
            // }
            // if (c == flash_led)
            // {
            //     write_char_data[0] = 1;
            //     // printf("flash_led \n");
            // }
            if (c == request_info)
            {
                uart_write_bytes(EX_UART_NUM, info_str, sizeof(info_str));
            }
        }
    }
}

static void deal_with_uart_data(uint8_t *p_data, uint16_t len)
{
    // please note that the programm only handle the case that we have one json in one uart-event,
    // I used free to release the node, so if we have one valid json-cmd, then I will excute it, then release the node,
    // then the whole json would break.
    // so it can not handler a uart-event with multiple json in it.

    // later I did not use cjson_free, instead, I used cjson_delete in the proper position, but still, handle only one json in one
    // uart event

    cJSON *cjson_root = NULL;
    cjson_root = cJSON_ParseWithLength((char *)p_data, len);

    // uart_write_bytes(EX_UART_NUM, p_data, len);

    if (cjson_root == NULL)
    {
        cJSON_Delete(cjson_root);
        return;
    }

    cJSON *cmd_new_ip = cJSON_GetObjectItem(cjson_root, new_ip_key_str);
    if (cmd_new_ip != NULL)
    {
        ESP_ERROR_CHECK(nvs_set_str(my_nvs_handler, nvs_ip_str, cmd_new_ip->valuestring));
        ESP_ERROR_CHECK(nvs_commit(my_nvs_handler));
        ESP_LOGI(TAG, "ip setting : %s", cmd_new_ip->valuestring);
        cJSON_Delete(cjson_root);
        return;
    }
    cJSON *cmd_ssid = cJSON_GetObjectItem(cjson_root, ssid_key_str);
    if (cmd_ssid != NULL)
    {
        ESP_ERROR_CHECK(nvs_set_str(my_nvs_handler, nvs_ssid_str, cmd_ssid->valuestring));
        ESP_ERROR_CHECK(nvs_commit(my_nvs_handler));
        ESP_LOGI(TAG, "ssid setting : %s", cmd_ssid->valuestring);

        cJSON_Delete(cjson_root);
        return;
    }
    cJSON *cmd_pw = cJSON_GetObjectItem(cjson_root, pw_key_str);
    if (cmd_pw != NULL)
    {
        ESP_ERROR_CHECK(nvs_set_str(my_nvs_handler, nvs_pw_str, cmd_pw->valuestring));
        ESP_ERROR_CHECK(nvs_commit(my_nvs_handler));
        ESP_LOGI(TAG, "pw setting : ********");

        cJSON_Delete(cjson_root);
        return;
    }
    cJSON *cmd_port = cJSON_GetObjectItem(cjson_root, port_key_str);
    if (cmd_port != NULL)
    {
        ESP_ERROR_CHECK(nvs_set_str(my_nvs_handler, nvs_port_str, cmd_port->valuestring));
        ESP_ERROR_CHECK(nvs_commit(my_nvs_handler));
        ESP_LOGI(TAG, "port setting : %s", cmd_port->valuestring);

        cJSON_Delete(cjson_root);
        return;
    }
    cJSON *request_json = cJSON_GetObjectItem(cjson_root, cmd_request_str);
    if (request_json != NULL)
    {
        m_uart_cmd_t c = not_valid_cmd;
        if (memcmp(cmd_request, request_json->valuestring, sizeof(cmd_request)) == 0)
        {
            c = request_info;
        }
        if (c != not_valid_cmd)
        {
            if (xQueueSendToBack(cmd_queue, &c, 0) != pdPASS)
            {
                // printf("error here \n");
            }
        }
        else
        {
            // printf("cmp failed\n");
        }
        // cJSON_free(request_json);
        // cJSON_free(cjson_root);
        cJSON_Delete(cjson_root);

        return;
    }

    // other cmd we do not define here,just ignore it.
    cJSON_Delete(cjson_root);
}

static void uart_event_task(void *pvParameters)
{
    uart_event_t event;
    size_t buffered_size;
    uint8_t *dtmp = (uint8_t *)malloc(RD_BUF_SIZE);
    for (;;)
    {
        // Waiting for UART event.
        if (xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY))
        {
            bzero(dtmp, RD_BUF_SIZE);
            // ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
            switch (event.type)
            {
            // Event of UART receving data
            /*We'd better handler data event fast, there would be much more data events than
            other types of events. If we take too much time on data event, the queue might
            be full.*/
            case UART_DATA:
                // uart_write_bytes(EX_UART_NUM, (const char *)str, sizeof(str));

                // ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
                uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
                // ESP_LOGI(TAG, "[DATA EVT]:");
                // uart_write_bytes(EX_UART_NUM, (const char *)dtmp, event.size);
                deal_with_uart_data(dtmp, event.size);

                break;
            // Event of HW FIFO overflow detected
            case UART_FIFO_OVF:
                ESP_LOGI(TAG, "hw fifo overflow");
                // If fifo overflow happened, you should consider adding flow control for your application.
                // The ISR has already reset the rx FIFO,
                // As an example, we directly flush the rx buffer here in order to read more data.
                uart_flush_input(EX_UART_NUM);
                xQueueReset(uart0_queue);
                break;
            // Event of UART ring buffer full
            case UART_BUFFER_FULL:
                ESP_LOGI(TAG, "ring buffer full");
                // If buffer full happened, you should consider increasing your buffer size
                // As an example, we directly flush the rx buffer here in order to read more data.
                uart_flush_input(EX_UART_NUM);
                xQueueReset(uart0_queue);
                break;
            // Event of UART RX break detected
            case UART_BREAK:
                ESP_LOGI(TAG, "uart rx break");
                break;
            // Event of UART parity check error
            case UART_PARITY_ERR:
                ESP_LOGI(TAG, "uart parity error");
                break;
            // Event of UART frame error
            case UART_FRAME_ERR:
                ESP_LOGI(TAG, "uart frame error");
                break;
            // UART_PATTERN_DET
            case UART_PATTERN_DET:
                uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
                int pos = uart_pattern_pop_pos(EX_UART_NUM);
                ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
                if (pos == -1)
                {
                    // There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not
                    // record the position. We should set a larger queue size.
                    // As an example, we directly flush the rx buffer here.
                    uart_flush_input(EX_UART_NUM);
                }
                else
                {
                    uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
                    uint8_t pat[PATTERN_CHR_NUM + 1];
                    memset(pat, 0, sizeof(pat));
                    uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
                    ESP_LOGI(TAG, "read data: %s", dtmp);
                    ESP_LOGI(TAG, "read pat : %s", pat);
                }
                break;
            // Others
            default:
                ESP_LOGI(TAG, "uart event type: %d", event.type);
                break;
            }
        }
    }
    free(dtmp);
    dtmp = NULL;
    vTaskDelete(NULL);
}

esp_err_t nvs_check_get_str(nvs_handle_t c_handle, const char *key, char *out_value, int len)
{
    size_t required_size;

    esp_err_t ret = nvs_get_str(c_handle, key, NULL, &required_size);
    switch (ret)
    {
    case ESP_OK:
        if (required_size > len)
        {
            ESP_LOGE(TAG, "the string too long");
            break;
        }
        else
        {
            memset(out_value, 0, len);
            nvs_get_str(my_nvs_handler, key, out_value, &required_size);

            ESP_LOGI(TAG, "read suc ---------  %s", out_value);

            return ret;
            // char *s = malloc(required_size);
            // Example (without error checking) of using nvs_get_str to get a string into dynamic array:
            // ret = nvs_get_str(my_nvs_handler, nvs_ip_str, s, &required_size);
            // memcpy(gl_ip_str, s, required_size);
        }
    case ESP_ERR_NVS_NOT_FOUND:
        // printf("The value is not initialized yet!\n");
        break;
    default:
        // printf("Error (%s) reading!\n", esp_err_to_name(ret));
    }
    return ret;
}
// int gpio_counter = 0;
void vTimerCallback(TimerHandle_t xTimer)
{
    if(get_btn_level(GPIO_BTN_NUM)==0){
        return;
    }
    // if(led_flicker_count==0){

    //     return;
    // }
    // led_flicker_count--;
    set_led_level(1);//turn off the led
    xTimerStopFromISR(xTimers_led,0);
}

// void soft_timer_init(void)
// {
// }

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    if (xTimers_led != NULL)
    {
        xTimerStart(xTimers_led, 0);
        set_led_level(0);//light up the led
    }
    // led_toggle();
    
    // uint32_t gpio_num = (uint32_t) arg;
    // if (xQueueIsQueueFullFromISR(gpio_evt_queue)==pdFALSE){
    //     xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
    // }
}
// static void gpio_task_example(void* arg)
// {
//     uint32_t io_num;
//     for (;;) {
//         if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
//             // printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
//             // ESP_LOGI(TAG, "GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
//             ESP_LOGI(TAG, "btn");
//         }
//     }
// }

char buff_task_list[512]={0}; 
void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    esp_err_t ret = nvs_open("storage", NVS_READWRITE, &my_nvs_handler);
    if (ret != ESP_OK)
    {
        // printf("Error (%s) opening NVS handle!\n", esp_err_to_name(ret));
    }
    else
    {
        if (nvs_check_get_str(my_nvs_handler, (const char *)nvs_ip_str, gl_ip_str, sizeof(gl_ip_str)) != ESP_OK)
        {
            ESP_LOGI(TAG, "%s not set yet", nvs_ip_str);
        }
        if (nvs_check_get_str(my_nvs_handler, (const char *)nvs_port_str, gl_port_str, sizeof(gl_port_str)) != ESP_OK)
        {
            ESP_LOGI(TAG, "%s not set yet", nvs_port_str);
        }
        if (nvs_check_get_str(my_nvs_handler, (const char *)nvs_ssid_str, gl_ssid_str, sizeof(gl_ssid_str)) != ESP_OK)
        {
            ESP_LOGI(TAG, "%s not set yet", nvs_ssid_str);
        }
        if (nvs_check_get_str(my_nvs_handler, (const char *)nvs_pw_str, gl_pw_str, sizeof(gl_pw_str)) != ESP_OK)
        {
            ESP_LOGI(TAG, "%s not set yet", nvs_pw_str);
        }
    }

    led_init();
    set_led_level(0);
    vTaskDelay(300 / portTICK_PERIOD_MS);
    set_led_level(1);
    vTaskDelay(300 / portTICK_PERIOD_MS);
    set_led_level(0);
    vTaskDelay(300 / portTICK_PERIOD_MS);
    set_led_level(1);

    btn_init(GPIO_NUM_5);
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    gpio_isr_handler_add(GPIO_NUM_5, gpio_isr_handler, (void*) GPIO_NUM_5);

    xTimers_led = xTimerCreate("Timer", 1000 / portTICK_PERIOD_MS, pdTRUE, (void *)0, vTimerCallback);
    gpio_init();

    wifi_x_init();
    get_mac_str(gl_mac_str);

    // -------------------------- - - ---------------------
    cmd_queue = xQueueCreate(CMD_QUEUE_LENGTH, CMD_QUEUE_ITEM_SIZE);
    data_queue = xQueueCreate(DATA_QUEUE_LENGTH, DATA_QUEUE_ITEM_SIZE);

    // uart_send_queue = xQueueCreate(uart_send_QUEUE_LENGTH, uart_send_QUEUE_ITEM_SIZE);
    // gpio_evt_queue = xQueueCreate(4, sizeof(uint32_t));

    uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
    
    xTaskCreate(uart_event_task, "uart_event_task", 1024*2 , NULL, 3, NULL);
    xTaskCreate(m_deal_uart_cmd_task, "m_deal_uart_cmd_task", 1024 , NULL, 2, NULL);
    xTaskCreate(deal_timer_task, "deal_timer_task", 1024 * 4, NULL, 2, &timer_task);
    xTaskCreate(udp_client_rcv_task, "udp_client_rcv", 1024*2, NULL, 4, NULL);
    // xTaskCreate(gpio_task_example, "gpio_task_example", 1024, NULL, 1, NULL);
    xTaskCreate(send_json_data_task, "send_json_data_task", 1024 * 4, NULL, 1, NULL);

    ad768x_init();
    amp_enable();

    gptimer_event_callbacks_t cbs = {
        .on_alarm = example_timer_on_alarm_cb, // register user callback
    };
    timer_x_init(&cbs);

    // xTimerStart(xTimers_led, 0);

    while (1)
    {
        vTaskDelay(5000 / portTICK_PERIOD_MS);

        // vTaskList(buff_task_list);
        // printf("----------------------------------------------\n");
        // printf("Name        State   Priority    Stack   Num\n");
        // printf("%s\n", buff_task_list);

        // UBaseType_t uxNumberOfFreeSpaces;
        // /* How many free spaces are currently available in the queue referenced by the
        // xQueue handle? */
        // uxNumberOfFreeSpaces = uxQueueSpacesAvailable( uart_send_queue );
        // printf(" ----- : %d \n",uxNumberOfFreeSpaces);
    }
}
